package com.yanjiali.service.impl;

import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import com.yanjiali.config.MinioConfig;
import com.yanjiali.enumerate.FileTypeEnum;
import com.yanjiali.model.req.FileUploadReq;
import com.yanjiali.model.resp.FileUploadResp;
import com.yanjiali.model.resp.WebDirectResp;
import com.yanjiali.properties.MinioBucketProperties;
import com.yanjiali.service.FileUploadService;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.MinioClient;
import io.minio.UploadObjectArgs;
import io.minio.http.Method;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

/**
 * @Package: com.yanjiali.service.impl
 * @Author: yanjiali
 * @Created: 2025/4/13 20:02
 * 文件上传业务实现类
 */
@Slf4j
@Service
public class FileUploadServiceImpl implements FileUploadService {

    @Autowired
    private MinioClient minioClient;

    @Autowired
    private MinioBucketProperties minioBucketProperties;

    @Autowired
    private MinioConfig minioConfig;

    public WebDirectResp generatePresignedUrl(String filename, FileTypeEnum fileTypeEnum, int expirySeconds) {
        if (!isFileExtensionAllowed(filename, fileTypeEnum)) {
            throw new IllegalArgumentException("不支持的文件类型: " + filename);
        }

        try {
            String bucketName = fileTypeEnum.getTypeName(); //获取对应的桶名
            String presignedUrl = minioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .method(Method.PUT)
                            .bucket(bucketName)
                            .object(filename)
                            .expiry(expirySeconds)
                            .build());
            return new WebDirectResp(presignedUrl);
        } catch (Exception e) {
            throw new RuntimeException("生成 Presigned URL 失败", e);
        }
    }

    @Override
    public FileUploadResp fileUpload(FileUploadReq fileUploadReq) throws IOException {
        MultipartFile filedata = fileUploadReq.getFiledata();
        String contentType = filedata.getContentType();
        String bucket = getBucketsThroughMIME(contentType); //获取桶
        if(bucket == null) {
            throw new IllegalArgumentException("目前无法识别的文件类型");
        }

        File tempFile = File.createTempFile("minio", ".temp");  //创建临时文件
        filedata.transferTo(tempFile);  //将filedata文件数据拷贝到临时文件
        String localFilePath = tempFile.getAbsolutePath();

        String fileName = filedata.getOriginalFilename();
        String extension = fileName.substring(fileName.lastIndexOf("."));
        String mimeType = getMimeType(extension);
        String folderPath = getDefaultFolderPath();
        String fileMd5 = getFileMd5(new File(localFilePath));
        String objectName = folderPath + fileMd5 + extension;

        //TODO 将文件上传到minio
        boolean result = addMediaFilesToMinIO(localFilePath, mimeType, bucket, objectName);
        if(!result) {
            throw new RuntimeException("上传文件失败");
        }

        //TODO 如果要记录对应上传的文件信息如提供文件表，那可以直接在这里记录

        //封装对应返回对象返回
        return new FileUploadResp(minioConfig.getEndpoint() + "/" + bucket + "/" +  objectName);
    }

    /**
     * 上传文件到minio
     * @param localFilePath 文件本地路径
     * @param mimeType 媒体类型
     * @param bucket 桶
     * @param objectName 对象名
     * @return
     */
    private boolean addMediaFilesToMinIO(String localFilePath, String mimeType, String bucket, String objectName){
        try {

            //创建上传对象
            UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
                    .bucket(bucket)     //确定操作的桶
                    .filename(localFilePath) //指定本地文件路径
                    .object(objectName)   //创建子目录
                    .contentType(mimeType) //设置媒体文件类型
                    .build();

            minioClient.uploadObject(uploadObjectArgs);
            log.debug("上传文件到minio成功,bucket:{}, objectName:{}", bucket, objectName);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            log.error("上传文件出错, bucket:{}, objectName:{}, 错误信息:{}", bucket, objectName, e.getMessage());
        }

        return false;
    }

    /**
     * 通过MIME类型获取桶名
     * @param contentType
     * @return
     */
    private String getBucketsThroughMIME(String contentType) {
        String[] split = contentType.split("/");
        String suffix = split[split.length - 1];

        for(FileTypeEnum fileTypeEnum : FileTypeEnum.values()) {
            for(String s : fileTypeEnum.getAllowedExtensions()) {
                if (s.equals(suffix)) {
                    return fileTypeEnum.getTypeName();
                }
            }
        }

        return null;
    }

    /**
     * 通过文件获取对应的MD5值
     * @return
     */
    private String getFileMd5(File file){
        try (FileInputStream fileInputStream = new FileInputStream(file)){
            String fileMd5 = DigestUtils.md5Hex(fileInputStream);
            return fileMd5;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据扩展名获取minmeType
     * @param extension
     * @return
     */
    private String getMimeType(String extension){
        if(extension == null){  //为空时，表示没有扩展名
            extension = "";
        }

        //1、通过扩展名得到媒体资源类型 mimeType (使用的一个第三方工具 去pom中查看)
        ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);
        String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE; //设置默认值未知的，有些文件无法获取扩展名

        //2、判断是否获取成功
        if (extensionMatch != null) {
            mimeType = extensionMatch.getMimeType();
        }

        return mimeType;
    }

    /**
     * 根据当前的时间获取对应的文件路径层级
     * @return
     */
    private String getDefaultFolderPath(){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        String folder = sdf.format(new Date()).replace("-", "/") + "/";
        //比如：2024-1-29 ==> 2024/1/29/
        return folder;
    }

    /**
     * 检查文件后缀是否合法
     * @param filename
     * @param fileTypeEnum
     * @return
     */
    private boolean isFileExtensionAllowed(String filename, FileTypeEnum fileTypeEnum) {
        String fileExtension = filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();
        return Arrays.stream(fileTypeEnum.getAllowedExtensions())
                .anyMatch(ext -> ext.equalsIgnoreCase(fileExtension));
    }
}
